package com.ripservice.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ripcore.engine.DefaultSimulationEngine;
import com.ripcore.engine.SimulationConfig;
import com.ripcore.model.Router;
import com.ripcore.model.RoutingTable;
import com.ripservice.dto.RoutingTableDto;
import com.ripservice.dto.SimulationDto;
import com.ripservice.entity.RouteSnapshotEntity;
import com.ripservice.entity.SimulationEntity;
import com.ripservice.mapper.RouteSnapshotMapper;
import com.ripservice.mapper.SimulationMapper;
import com.ripservice.mapper.TopologyMapper;
import jakarta.annotation.PreDestroy;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 仿真服务类，负责管理RIP协议仿真实例的生命周期，包括启动、停止、配置和路由表查询等功能。
 * 同时处理仿真过程中产生的路由表变化事件，将其通过WebSocket推送至前端并持久化存储。
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SimulationService {

    /**
     * WebSocket消息模板，用于向客户端推送路由表更新等实时信息
     */
    private final SimpMessagingTemplate messagingTemplate;

    /**
     * 拓扑结构数据访问对象，用于读取网络拓扑信息（路由器和链路）
     */
    private final TopologyMapper topologyMapper;

    /**
     * 路由快照数据访问对象，用于保存路由表的历史状态
     */
    private final RouteSnapshotMapper routeSnapshotMapper;

    /**
     * 仿真数据访问对象，用于保存仿真信息（如仿真ID、仿真状态、仿真配置等）
     */
    private final SimulationMapper simulationMapper;
    
    /** Spring-managed ObjectMapper so JavaTimeModule is used for Instant serialization */
    private final ObjectMapper objectMapper;

    /**
     * 仿真中路由更新的间隔时间（秒），可通过配置文件自定义，默认为5秒
     */
    @Value("${rip.simulation.update-interval-seconds:5}")
    private int updateIntervalSeconds;

    /**
     * 路由条目的超时时间（秒），超过该时间未更新的路由将被标记为不可达，默认为30秒
     */
    @Value("${rip.simulation.route-timeout-seconds:30}")
    private int routeTimeoutSeconds;

    /**
     * 存储活跃的仿真引擎实例映射，键为仿真ID，值为对应的DefaultSimulationEngine实例
     */
    // simulationId -> engine
    private final Map<String, DefaultSimulationEngine> engines = new ConcurrentHashMap<>();

    /**
     * 存储各仿真实例的配置信息，键为仿真ID，值为对应的SimulationConfig配置对象
     */
    // simulation configuration storage (per-simulation config)
    private final Map<String, SimulationConfig> simConfigs = new ConcurrentHashMap<>();

    /** Pending routing table updates keyed by "{simId}|{routerId}"; latest update wins. */
    private final ConcurrentMap<String, RoutingTableDto> pendingSnapshots = new ConcurrentHashMap<>();

    /** Scheduler to flush pending snapshots to WebSocket + DB in batches. */
    private final ScheduledExecutorService snapshotFlusher = Executors.newSingleThreadScheduledExecutor(r -> {
        Thread t = new Thread(r, "snapshot-flusher");
        t.setDaemon(true);
        return t;
    });

    /** Interval in milliseconds to flush pending snapshots (configurable). */
    @Value("${rip.simulation.snapshot-flush-ms:250}")
    private int snapshotFlushIntervalMs;

    /**
     * 根据指定的拓扑ID启动一个新的RIP协议仿真实例
     *
     * @param topologyId 拓扑结构的唯一标识符
     * @return 成功启动的仿真所对应的拓扑ID
     * @throws IllegalArgumentException 当找不到对应拓扑时抛出异常
     */
    public Long startSimulation(Long topologyId) {
        // 从数据库加载指定ID的拓扑结构信息
        var topo = topologyMapper.findById(topologyId);
        // 如果未找到对应拓扑，则抛出非法参数异常
        if (topo == null) throw new IllegalArgumentException("Topology not found: " + topologyId);

        // 读取该拓扑下的所有路由器和链路信息
        var routers = topologyMapper.findRoutersByTopology(topologyId);
        var links = topologyMapper.findLinksByTopology(topologyId);

        // 创建路由器映射表，用于后续建立邻居关系
        Map<String, Router> routerMap = new HashMap<>();
        for (var r : routers) {
            Router router = new Router(r.getRouterId());
            routerMap.put(r.getRouterId(), router);
        }

        // 插入仿真记录（将使用数据库生成的ID作为仿真ID）
        SimulationEntity simEntity = new SimulationEntity();
        simEntity.setTopologyId(topologyId);
        simEntity.setStatus("RUNNING");
        simEntity.setStartedAt(Instant.now());
        simEntity.setCreatedAt(Instant.now());
        simulationMapper.insertSimulation(simEntity);

        // 获取数据库分配的仿真id，并作为后续使用的key
        String simKey = String.valueOf(simEntity.getId());

        // 获取当前仿真的配置，若未设置则使用默认配置（按仿真ID键）
        SimulationConfig config = simConfigs.getOrDefault(simKey,
                SimulationConfig.defaultConfig(updateIntervalSeconds, routeTimeoutSeconds));

        // 创建新的仿真引擎实例
        DefaultSimulationEngine engine = new DefaultSimulationEngine(simKey, config);

        // 将之前创建的Router对象添加到引擎中
        for (Router router : routerMap.values()) {
            engine.addRouter(router);
        }

        // 建立路由器之间的双向连接（链路）
        for (var l : links) {
            Router a = routerMap.get(l.getFromRouter());
            Router b = routerMap.get(l.getToRouter());
            if (a != null && b != null) {
                int cost = l.getCost() == null ? 1 : l.getCost();
//                a.addNeighbor(b.getId(), cost); // 不知道为什么将这句话注释就不会把metric调换了
                b.addNeighbor(a.getId(), cost);
            }
        }

        // 设置仿真事件监听器：将更新放入 pendingSnapshots，由后台定时器周期性合并并批量发送/保存
        engine.setListener((ignore, routerId, routingTable) -> {
            RoutingTableDto dto = toDto(routerId, routingTable);
            String key = simKey + "|" + routerId;
            pendingSnapshots.put(key, dto);
        });

        // 将新创建的引擎实例存入映射表中（key为数据库分配的仿真ID）
        engines.put(simKey, engine);
        // 启动仿真引擎
        engine.start();
        log.info("仿真引擎已启动 - 仿真ID: {}, 路由器数量: {}", simKey, routerMap.size());

        // 发送所有路由器的初始路由表状态
        for (Router router : routerMap.values()) {
            RoutingTableDto dto = toDto(router.getId(), router.getRoutingTable());
            String topic = "/topic/simulations/" + simKey + "/routers/" + router.getId() + "/routing-table";
            log.info("发送初始路由表状态 - 仿真ID: {}, 路由器ID: {}, 主题: {}", simKey, router.getId(), topic);
            messagingTemplate.convertAndSend(topic, dto);
        }

        // 返回数据库生成的仿真ID作为成功启动的标识
        return simEntity.getId();
    }

    @PostConstruct
    private void startSnapshotFlusher() {
        snapshotFlusher.scheduleAtFixedRate(this::flushPendingSnapshots,
                snapshotFlushIntervalMs, snapshotFlushIntervalMs, TimeUnit.MILLISECONDS);
    }

    /** Drain pendingSnapshots and send & persist each entry. */
    private void flushPendingSnapshots() {
        try {
            if (pendingSnapshots.isEmpty()) return;
            for (Map.Entry<String, RoutingTableDto> entry : pendingSnapshots.entrySet()) {
                String key = entry.getKey();
                RoutingTableDto dto = entry.getValue();
                // attempt to remove that exact mapping; if removed, we process it
                if (!pendingSnapshots.remove(key, dto)) continue;

                try {
                    String[] parts = key.split("\\|", 2);
                    String simKey = parts[0];
                    String routerId = parts.length > 1 ? parts[1] : "";
                    String topic = "/topic/simulations/" + simKey + "/routers/" + routerId + "/routing-table";
                    log.info("发送WebSocket消息 - 仿真ID: {}, 路由器ID: {}, 主题: {}", simKey, routerId, topic);
                    messagingTemplate.convertAndSend(topic, dto);

                    RouteSnapshotEntity se = new RouteSnapshotEntity();
                    se.setSimulationId(Long.valueOf(simKey));
                    se.setRouterId(routerId);
                    se.setSnapshotTime(Instant.now());
                    se.setRouteJson(objectMapper.writeValueAsString(dto));
                    routeSnapshotMapper.insert(se);
                } catch (Exception ex) {
                    log.error("批量处理路由快照失败 - key: {}", key, ex);
                }
            }
        } catch (Exception ex) {
            log.error("flushPendingSnapshots exception", ex);
        }
    }

    /**
     * 查询特定仿真中某台路由器的当前路由表信息
     *
     * @param simulationId 仿真实例ID
     * @param routerId     路由器ID
     * @return 包含路由表信息的数据传输对象，若未找到则返回null
     */
    public RoutingTableDto getRoutingTable(String simulationId, String routerId) {
        // 根据仿真ID获取对应的仿真引擎实例
        DefaultSimulationEngine e = engines.get(simulationId);
        // 如果引擎不存在，则返回null
        if (e == null) return null;
        // 从引擎中获取指定ID的路由器实例
        Router r = e.getRouter(routerId);
        // 如果路由器不存在，则返回null
        if (r == null) return null;
        // 将路由器的路由表转换为DTO对象并返回
        return toDto(routerId, r.getRoutingTable());
    }

    /**
     * 将核心模块中的RoutingTable对象转换为DTO对象以便于传输和展示
     *
     * @param routerId     路由器ID
     * @param routingTable 核心模型中的路由表对象
     * @return DTO形式的路由表对象
     */
    private RoutingTableDto toDto(String routerId, RoutingTable routingTable) {
        // 遍历路由表中的所有条目，将其转换为RouteDto对象
        List<RoutingTableDto.RouteDto> list = routingTable
                .entries()
                .stream()
                .map(e ->
                        new RoutingTableDto.RouteDto(
                                e.getDestination(),   // 目标网络地址
                                e.getMetric(),        // 跳数度量值
                                e.getNextHop(),       // 下一跳路由器地址
                                e.getLastUpdated()    // 路由条目最后更新时间
                        )
                ).collect(Collectors.toList()); // 收集转换后的RouteDto对象到列表中
        // 创建并返回包含路由器ID和路由条目列表的RoutingTableDto对象
        return new RoutingTableDto(routerId, list);
    }

    /**
     * 应用关闭前的清理工作，确保所有正在进行的仿真都被正确停止
     */
    @PreDestroy
    public void shutdown() {
        // 遍历所有活跃的仿真引擎实例，并调用其stop方法停止运行
        engines.values().forEach(DefaultSimulationEngine::stop);
        // flush remaining pending snapshots and stop flusher
        try {
            flushPendingSnapshots();
        } catch (Exception e) {
            log.warn("Exception while flushing pending snapshots during shutdown", e);
        }
        snapshotFlusher.shutdown();
        try {
            if (!snapshotFlusher.awaitTermination(2, TimeUnit.SECONDS)) {
                snapshotFlusher.shutdownNow();
            }
        } catch (InterruptedException ie) {
            snapshotFlusher.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }

    /**
     * 设置指定仿真实例的配置参数
     *
     * @param simulationId 仿真实例ID
     * @param config       新的仿真配置对象
     */
    public void setSimulationConfig(String simulationId, SimulationConfig config) {
        // 将指定仿真ID的配置信息存储到配置映射中
        simConfigs.put(simulationId, config);
    }

    /**
     * 获取指定仿真实例的当前配置，如无特殊设置则返回默认配置
     *
     * @param simulationId 仿真实例ID
     * @return 仿真配置对象
     */
    public SimulationConfig getSimulationConfig(String simulationId) {
        // 从配置映射中获取指定仿真ID的配置，如果不存在则返回默认配置
        return simConfigs.getOrDefault(
                simulationId,
                SimulationConfig.defaultConfig(updateIntervalSeconds, routeTimeoutSeconds)
        );
    }

    /**
     * 停止指定ID的仿真实例并释放相关资源
     * 1.从引擎映射中移除并获取对应的仿真引擎实例，如果引擎存在，则停止其运行
     * 2.查找对应的仿真实体记录，如果记录存在，则更新其状态为"STOPPED"并记录停止时间
     *
     * @param simulationId 仿真实例ID
     */
    public void stopSimulation(String simulationId) {
        // 从引擎映射中获取并移除仿真引擎
        DefaultSimulationEngine engine = engines.remove(simulationId);
        if (engine != null) {
            // 停止仿真引擎
            engine.stop();
        }

        // 更新数据库中的仿真记录状态
        try {
            Long simId = Long.valueOf(simulationId);
            SimulationEntity entity = simulationMapper.findById(simId);
            if (entity != null) {
                entity.setStatus("STOPPED");
                entity.setStoppedAt(Instant.now());
                simulationMapper.updateSimulation(entity);
            }
        } catch (NumberFormatException e) {
            // 如果simulationId不是数字，忽略数据库更新
        }
    }

    /**
     * 获取所有仿真实例的历史记录列表
     *
     * @return 所有仿真实例的列表
     */
    public List<SimulationEntity> getAllSimulations() {
        return simulationMapper.findAllSimulations();
    }

    /**
     * 根据拓扑ID获取相关的所有仿真实例记录
     *
     * @param topologyId 拓扑ID
     * @return 该拓扑下的所有仿真实例列表
     */
    public List<SimulationEntity> getSimulationsByTopology(Long topologyId) {
        return simulationMapper.findSimulationsByTopology(topologyId);
    }

    /**
     * 删除指定ID的仿真实例及其相关数据(快照也要删除)
     * 注：先停止仿真再依次删除快照、仿真记录
     *
     * @param simulationId 仿真实例ID
     */
    public void deleteSimulation(String simulationId) {
        // 先停止仿真
        stopSimulation(simulationId);

        // 删除快照
        try {
            Long simId = Long.valueOf(simulationId);
            routeSnapshotMapper.deleteBySimulationId(simId);

            // 删除仿真记录
            simulationMapper.deleteSimulation(simId);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid simulation ID: " + simulationId);
        }
    }
}